﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using XmlStudio.Enums;
using XmlStudio.Interfaces;
using System;

namespace XmlStudio.DataLayer {
    /// <summary>
    /// Represents xml node used in BigXmlDocument.
    /// </summary>
    [DebuggerDisplay("{Name} - modified: {Modified}, node state: {NodeState}")]
    public abstract class BigXmlNode {
        #region Fields
        /// <summary>
        /// Address.
        /// </summary>
        protected int[] address;

        /// <summary>
        /// Original address.
        /// </summary>
        protected int[] originalAddress;

        /// <summary>
        /// Collection of child nodes.
        /// </summary>
        protected List<BigXmlNode> childNodes = new List<BigXmlNode>();

        /// <summary>
        /// Parent.
        /// </summary>
        protected BigXmlNode parent;
        
        /// <summary>
        /// Indicates whether this node is modified.
        /// </summary>
        protected bool modified = false;

        /// <summary>
        /// Child count.
        /// </summary>
        protected int childCount = 0;

        /// <summary>
        ///Attributes. 
        /// </summary>
        protected Dictionary<string, IBigXmlAttribute> attributes = new Dictionary<string, IBigXmlAttribute>();
        
        /// <summary>
        /// <see cref="XmlNode"/>.
        /// </summary>
        protected XmlNode xmlNode;
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the appearance.
        /// </summary>
        public IAppearance Appearance { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="BigXmlNode"/>'s address.
        /// </summary>
        public int[] Address {
            get { return this.address; }
        }

        /// <summary>
        /// Gets the collection of attributes.
        /// </summary>
        public BigXmlAttributeCollection Attributes {
            get { return new BigXmlAttributeCollection(this.attributes); }
        }

        /// <summary>
        /// Gets or sets the node's state.
        /// </summary>
        public ExpandState NodeState {
            get {
                ExpandState nodeState;
                if(this.originalAddress == null) {
                    nodeState = ExpandState.Expanded;
                } else {
                    if(this.ExpandedRange.Count() == 0 && this.TotalChildCount > 0) {
                        nodeState = ExpandState.Collapsed;
                    } else if(this.ExpandedRange.Count() == this.TotalChildCount) {
                        nodeState = ExpandState.Expanded;
                    } else {
                        nodeState = ExpandState.PartiallyExpanded;
                    }
                }

                return nodeState;
            }
        }

        /// <summary>
        /// Gets or sets the value indicating whether the current node has been modified.
        /// </summary>
        public virtual bool Modified {
            get { return this.modified; }
        }

        /// <summary>
        /// Gets the total number of child nodes.
        /// </summary>
        public int TotalChildCount {
            get { return this.childNodes.Count; }
        }

        /// <summary>
        /// Gets the node's original address.
        /// </summary>
        public int[] OriginalAddress {
            get { return this.originalAddress; }
        }

        /// <summary>
        /// Gets the name of the node.
        /// </summary>
        public string Name {
            get { return this.xmlNode.CreateNavigator().Name; }
        }

        /// <summary>
        /// Gets the local name of the node.
        /// </summary>
        public string LocalName {
            get { return this.xmlNode.CreateNavigator().LocalName; }
        }

        /// <summary>
        /// Gets the prefix of the node.
        /// </summary>
        public string Prefix {
            get { return this.xmlNode.CreateNavigator().Prefix; }
        }

        /// <summary>
        /// Gets the namespace uri of the node.
        /// </summary>
        public string NamespaceURI {
            get { return this.xmlNode.CreateNavigator().NamespaceURI; }
        }

        /// <summary>
        /// Gets the value of the node.
        /// </summary>
        public string Value {
            get { return this.xmlNode.Value; }
        }

        /// <summary>
        /// Gets the type of the node.
        /// </summary>
        public XmlNodeType NodeType {
            get { return this.xmlNode.NodeType; }
        }

        /// <summary>
        /// Gets the collection of child nodes.
        /// </summary>
        public IList<BigXmlNode> ChildNodes {
            get { return this.childNodes; }
        }

        /// <summary>
        /// Gets or sets the parent of the current node.
        /// </summary>
        public BigXmlNode Parent {
            get { return this.parent; }
        }

        /// <summary>
        /// Gets the expanded range string.
        /// </summary>
        public IEnumerable<int> ExpandedRange {
            get {
                List<int> range = new List<int>();

                foreach(var childNode in this.childNodes) {
                    if(childNode != null) {
                        range.Add(childNode.Address.Last());
                    }
                }

                return range;
            }
        }
        #endregion

        #region Methods
        #region Public
        /// <summary>
        /// Returns value indicating whether this node can be collapsed, so there is no modified child node.
        /// </summary>
        /// <returns>True if node can be collapsed, false otherwise.</returns>
        public bool CanCollapse() {
            bool canCollapse = !this.modified;

            if(canCollapse) {
                foreach(BigXmlNode child in this.childNodes) {
                    if(child != null && !child.CanCollapse()) {
                        canCollapse = false;
                        break;
                    }
                }
            }

            return canCollapse;
        }

        /// <summary>
        /// Gets the previous sibling of the specified child node.
        /// </summary>
        /// <param name="child"><see cref="BigXmlNode"/> which previous sibling to get.</param>
        /// <returns>Reference to the previous sibling or null if the child node is the first node.</returns>
        public BigXmlNode GetPreviousSibling(BigXmlNode child) {
            BigXmlNode previousSibling = null;
            int childIndex = this.childNodes.IndexOf(child);
            if(childIndex > 0) {
                previousSibling = this.childNodes[childIndex - 1];
            }

            return previousSibling;
        }

        /// <summary>
        /// Gets the next sibling of the specified child node.
        /// </summary>
        /// <param name="child"><see cref="BigXmlNode"/> which next sibling to get.</param>
        /// <returns>Reference to the next sibling or null if the child node is the last node.</returns>
        public BigXmlNode GetNextSibling(BigXmlNode child) {
            BigXmlNode nextSibling = null;
            int childIndex = this.childNodes.IndexOf(child);
            if(childIndex + 1 < this.childNodes.Count) {
                nextSibling = this.childNodes[childIndex + 1];
            }

            return nextSibling;
        }
        #endregion
        #endregion
    }
}